/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package jcompiler;

/**
 *
 * @author Gabriel Queiroz
 */

import java.util.List;
import java.util.ArrayList;
import token.Token;
import token.TokenAll;
import token.TokenType;

public class ControleAutomato {

    List<Token> listagem = new ArrayList<Token>();
    List<Erros> listErro = new ArrayList<Erros>();
    int ponteiro;
    int ESTADO;
    private int linha;
    char[] fonte;
    int coment = 0;
    MetodosDeEstado metodos;
    String quebras = " ,;\t+-*/=!><&|\"_(){}[].'";


    /**
     * Método principal que chama todos os estados do analisador léxico.
     */
    public void controle(String textoFonte){

        linha++;
        ESTADO = 0;
        ponteiro = 0;

        this.fonte = (textoFonte).toCharArray();

        do{
            switch(ESTADO){
                case 0:
                    primeiroEstado();
                    break;
                case 1:
                    identificador();
                    break;
                case 2:
                    numero();
                    break;
                case 3:
                    cadeiaConstante();
                    break;
                case 4:
                    comentario();
                    break;
                case 5:
                    delimitador();
                    break;
                case 6:
                    operador();
                    break;
                case 7:
                    caractereConstante();
                    break;
                case 8:
                    caractereInvalido();
                    break;
            }//fim do switch/case
        }while(this.incrementaPonteiro());
    }//fim do método controle.

    public int getESTADO() {
        return ESTADO;
    }

    public void setESTADO(int ESTADO) {
        this.ESTADO = ESTADO;
    }

    public int getComent() {
        return coment;
    }

    public void setComent(int coment) {
        this.coment = coment;
    }

    public char[] getFonte() {
        return fonte;
    }

    public void setFonte(char[] fonte) {
        this.fonte = fonte;
    }

    public int getLinha() {
        return linha;
    }

    public void setLinha(int linha) {
        this.linha = linha;
    }

    public List<Erros> getListErro() {
        return listErro;
    }

    public void setListErro(List<Erros> listErro) {
        this.listErro = listErro;
    }

    public List<Token> getListagem() {
        return listagem;
    }

    public void setListagem(List<Token> listagem) {
        this.listagem = listagem;
    }

    public MetodosDeEstado getMetodos() {
        return metodos;
    }

    public void setMetodos(MetodosDeEstado metodos) {
        this.metodos = metodos;
    }

    public int getPonteiro() {
        return ponteiro;
    }

    public void setPonteiro(int ponteiro) {
        this.ponteiro = ponteiro;
    }

    public String getQuebras() {
        return quebras;
    }

    public void setQuebras(String quebras) {
        this.quebras = quebras;
    }

    /**
     * Método usado para incrementar o ponteiro do código-fonte.
     * @return verdadeiro se ainda há caracteres ou falso caso contrário.
     */
    public boolean incrementaPonteiro(){
        if(ponteiro < fonte.length -1){
            ponteiro++;
            return true;
        }else{
            return false;
        }

    }//fim do método incrementa ponteiro.

    /**
     * Estado inicial para todos os autômatos.
     * Estado 0: estado inicial.
     * Estado 1: caracteres.
     * Estado 2: números.
     * Estado 3: cadeia constante.
     * Estado 4: comentário.
     * Estado 5: delimitador.
     * Estado 6: operador.
     * Estado 7: caractere constante.
     * Estado 8: caractere inválido.
     */
    public void primeiroEstado(){
        if(ponteiro == fonte.length){
            //caso tenha acabado, não faz nada.
        }else if((fonte[ponteiro] == ' ' || fonte[ponteiro] == '\t') && coment == 0){
            ESTADO = 0;
        }else if(coment == 1){
            //caso seja um comentário...
            ESTADO = 4;
        }else{
            if(Character.isLetter(fonte[ponteiro])){
                //caso seja uma letra, vai para o estado 1.
                ESTADO = 1;
                System.out.println("::identificador");
                ponteiro--;
            }else
                if(Character.isDigit(fonte[ponteiro])){
                    //caso seja um número, vai para o estado 2
                    ESTADO = 2;
                    ponteiro--;
                }else if(fonte[ponteiro] == '"'){
                    //caso seja aspas duplas (inicio de uma possível cadeia constante), vai para o estado 3.
                    ESTADO = 3;
                    ponteiro--;
                }else if(this.isDelimitador()){
                    ESTADO = 5;
                    ponteiro--;
                }else if(this.isOperador()){
                    ESTADO = 6;
                    ponteiro--;
                }else if(fonte[ponteiro] == 47){
                    System.out.println("::Achou uma barra!!! (1)");
                    try{
                       if(fonte[ponteiro+1] == 42){
                           System.out.println("::comentário de bloco.");
                           ponteiro++;

                           if(ponteiro == fonte.length-1){
                               coment = 1;
                               ESTADO = 0;
                           }else{
                               //coment = 1;
                               ESTADO = 4;
                           }
                       }else if(fonte[ponteiro+1] == 47){
                           saltarLinha();
                           ESTADO = 0;
                           ponteiro = fonte.length-1;
                       }else if(Character.isDigit(fonte[ponteiro+1])||Character.isLetter(fonte[ponteiro+1])||Character.isSpaceChar(fonte[ponteiro+1])){
                           System.out.println("é um operador de divisão!! (2)");
                           ESTADO = 6;
                           ponteiro--;
                       }
                    }catch(ArrayIndexOutOfBoundsException e){
                        ESTADO = 6;
                        ponteiro --;
                    }
                }else if(fonte[ponteiro] == 39){
                    ESTADO = 7;
                    ponteiro--;
                }else{
                    ESTADO = 8;
                    ponteiro--;
                }
        }
    }//fim do método primeiroEstado

    /**
     * Verifica se um caractere é um operador.
     * @return verdadeiro se for operador e falso caso contrário.
     */
    public boolean isOperador(){
        if(fonte[ponteiro] == 33 || fonte[ponteiro] == 38
                || fonte[ponteiro] == 42 || fonte[ponteiro] == 43
                || fonte[ponteiro] == 45 || fonte[ponteiro] == 60
                || fonte[ponteiro] == 61 || fonte[ponteiro] == 62
                || fonte[ponteiro] == 124 || fonte[ponteiro] == 46
                ){
            return true;
        }//fim do if
        return false;
    }// fim do método isOperador.

    /**
     * Verifica se o caractere é um delimitador.
     * @return verdadeiro se for delimitador e falso caso contrário.
     */
    public boolean isDelimitador(){
        if(fonte[ponteiro] == 59 || fonte[ponteiro] == 40
                || fonte[ponteiro] == 41 || fonte[ponteiro] == 91
                || fonte[ponteiro] == 93 || fonte[ponteiro] == 123
                || fonte[ponteiro] == 125 || fonte[ponteiro] == 44
                || fonte[ponteiro] == 46){
            return true;
        }
        return false;
    }

    /**
     * Pula uma linha do código (geralmente usado para comentários de linha).
     */
    public void saltarLinha(){
        while(fonte[ponteiro] != 3){
            ponteiro++;
        }
    }

    /**
     * Este método verifica se um determinado token é um identificador ou se há
     * algum erro de formação do mesmo.
     */
    public void identificador(){

        System.out.println("::Entrou em identificador!!!");

        String aux = "";
        int inicio = ponteiro;

        while(Character.isLetter(fonte[ponteiro]) || Character.isDigit(fonte[ponteiro]) || fonte[ponteiro] == '_'){
            aux = aux+fonte[ponteiro];
            if(!this.incrementaPonteiro()){
                break;
            }
        }//fim do while

        if(isValidChar(fonte[ponteiro]) && Character.isLetter(fonte[inicio])){
            if(aux.length() < 32){
                listagem.add(new Token(TokenAll.IDENTIFICADOR, aux, linha, TokenType.IDENTIFICADOR));
                ESTADO = 0;
                TokenAll[] tipos;
                tipos = TokenAll.values();
                for(int x = 0; x < 22; x++){
                    if(listagem.get(listagem.size()-1).getValor().equals(tipos[x].toString())){
                        listagem.get(listagem.size()-1).seteToke(TokenType.PALAVRACHAVE);
                        listagem.get(listagem.size()-1).setTipo(TokenAll.valueOf(aux));
                        break;
                    }
                }
                if(!isFimPonteiro() || !(Character.isLetter(fonte[ponteiro]))||Character.isDigit(fonte[ponteiro]) || fonte[ponteiro]=='_')
                ponteiro--;
            }else{
                this.listErro.add(new Erros("IDENTIFICADOR MUITO GRANDE", aux, linha));
            }

        }else{
            while(!isQuebra(fonte[ponteiro]) || isValidChar(fonte[ponteiro])){
                aux = aux+fonte[ponteiro];
                if(!this.incrementaPonteiro()){
                    break;
                }
            }
            this.listErro.add(new Erros("IDENTIFICADOR INVÁLIDO", aux, linha));
        }//fim do if/else

    }//fim do método identificador.

    /**
     * Verifica se um determinado caractere é válido.
     * @param c
     * Caractere a ser examinado.
     * @return
     * Verdadeiro se é um caractere válido e falso caso contrário.
     */
    public boolean isValidChar(char c){
        if(Character.isLetter(c) || Character.isDigit(c) || Character.isSpaceChar(c) || isQuebra(c)){
            if((c == '&' && fonte[ponteiro+1] == '&') || (c=='!' && fonte[ponteiro+1]=='=')){
                return false;
            }else
                return true;
        }else
            return false;
    }

    /**
     * Verifica se um determinado caractere pertence aos caracteres de quebra.
     * @param c
     * Caractere a ser examinado.
     * @return
     * Verdadeiro se pertence aos caracteres considerados de quebra e falso caso contrário.
     */
    private boolean isQuebra(char c){
        for(int i = 0; i < quebras.length(); i++){
            if(c == quebras.charAt(i)){
                return true;
            }
        }
        return false;
    }
    /**
     * Verifica se o contador chegou ao fim.
     * @return
     * Retorna verdadeiro se chegou ao fim e falso caso contrário.
     */
    private boolean isFimPonteiro(){
        if(ponteiro == fonte.length-1){
            return true;
        }else
            return false;
    }//fim do método isFimPonteiro

    /**
     * Este método verifica se um determinado token é um número ou se há alguma
     * má formação em um possível token de número.
     */
    private void numero(){
        System.out.println("::Entrou em numero!");
        String aux = "";
        int flag = 0;
        boolean isFloat = false;
        boolean isInvalid = false;
        float aux1;

        if(!isValidChar(fonte[ponteiro])){
            isInvalid = true;
            aux = aux+fonte[ponteiro];
            this.incrementaPonteiro();
        }//fim do if
        while(Character.isDigit(fonte[ponteiro])){
            aux = aux+fonte[ponteiro];
            if(!this.incrementaPonteiro())
                break;
        }//fim do while
        System.out.println("valor no aux: " + aux);
        if(fonte[ponteiro]=='.'){
            isFloat = true;
            aux = aux + '.';
            while(this.incrementaPonteiro() && Character.isDigit(fonte[ponteiro])){
                aux = aux + fonte[ponteiro];
            }//fim do while
        }//fim do if

        /*if(isFloat && fonte[ponteiro] == '.'){
            while(!Character.isSpaceChar(fonte[ponteiro]) || Character.isLetter(fonte[ponteiro]) || !this.isQuebra(fonte[ponteiro])){
                this.incrementaPonteiro();
                aux = aux + fonte[ponteiro];
            }
            this.listErro.add(new Erros("NÚMERO INVÁLIDO", aux, linha));
            ESTADO = 0;
            return;
        }*/

        if(Character.isLetter(fonte[ponteiro]) || !this.isValidChar(fonte[ponteiro]) || fonte[ponteiro]=='_' || isInvalid){
            flag = 1;
            while(Character.isLetter(fonte[ponteiro]) || !this.isValidChar(fonte[ponteiro]) || fonte[ponteiro]=='_' || isInvalid){
                this.incrementaPonteiro();
                aux = aux + fonte[ponteiro];
            }
            System.out.println("Erro numero!!");
            this.listErro.add(new Erros("NÚMERO INVÁLIDO", aux, linha));
            ESTADO = 0;
            return;
        }//fim do if
        aux1 = Float.parseFloat(aux);

        if(isFloat){
            if(Float.isInfinite(aux1)){
                this.listErro.add(new Erros("NÚMERO REAL MUITO GRANDE", aux, linha));
                ponteiro++;
                ESTADO = 0;
                return;
            }//fim do if infinity
        }else{
            try{
                int aux2 = Integer.parseInt(aux);
            }catch(NumberFormatException n){
                this.listErro.add(new Erros("NÚMERO INTEIRO MUITO GRANDE", aux, linha));
                ponteiro++;
                ESTADO = 0;
                return;
            }//fim do try/catch
        }//fim do if/else

        if(isFimPonteiro() && Character.isDigit(fonte[ponteiro])){ //mod || --> &&
            ponteiro++;
        }
        ponteiro--;
        ESTADO = 0;
        if(flag == 0){
            listagem.add(new Token(TokenAll.NUMERO, aux1, linha, TokenType.NUMERO));
        }

        flag = 0;

    }//fim do método número.

    /**
     * Verifica se um determinado token é um operador.
     */
    private void operador(){
        System.out.println("::Entrou em operador!");
        switch(fonte[ponteiro]){
            case 33:
                try{
                    if(fonte[ponteiro+1] == 61){
                        listagem.add(new Token(TokenAll.DIFERENCA, "!=", linha, TokenType.OPERADOR));
                        ponteiro++;
                    }else{
                        caractereInvalido();
                        ESTADO = 0;
                    }
                }catch(ArrayIndexOutOfBoundsException a){
                    listErro.add(new Erros("CARACTERE INVÁLIDO", String.valueOf(fonte[ponteiro]), linha));
                    ESTADO = 0;
                }finally{
                    break;
                }
            case 38:
                try{
                    if(fonte[ponteiro+1]==38){
                        listagem.add(new Token(TokenAll.E, "&&", linha, TokenType.OPERADOR));
                        ponteiro++;
                    }else{
                        caractereInvalido();
                        ESTADO = 0;
                    }
                }catch(ArrayIndexOutOfBoundsException a){
                    this.listErro.add(new Erros("OPERADOR INVÁLIDO", String.valueOf(fonte[ponteiro]), linha));
                    ESTADO = 0;
                }finally{
                    break;
                }
            case 42:
                listagem.add(new Token(TokenAll.MULTIPLICACAO, "*", linha, TokenType.OPERADOR));
                ESTADO = 0;
                break;
            case 43:
                if(!this.isFimPonteiro() && fonte[ponteiro+1] == 43){
                    listagem.add(new Token(TokenAll.INCREMENTO, "++", linha, TokenType.OPERADOR));
                    ponteiro++;
                }else{
                    listagem.add(new Token(TokenAll.SOMA, "+", linha, TokenType.OPERADOR));
                }
                break;
            case 45:
                if(!this.isFimPonteiro() && fonte[ponteiro+1] == 45){
                    listagem.add(new Token(TokenAll.DECREMENTO, "--", linha, TokenType.OPERADOR));
                    ponteiro++;
                }else{
                    listagem.add(new Token(TokenAll.SUBTRACAO, "-", linha, TokenType.OPERADOR));
                }
                break;
            case 46:
                listagem.add(new Token(TokenAll.PONTO, ".", linha, TokenType.OPERADOR));
                ESTADO = 0;
                break;
            case 47:
                listagem.add(new Token(TokenAll.DIVISAO, "/", linha, TokenType.OPERADOR));
                break;
            case 60:
                if(!this.isFimPonteiro() && fonte[ponteiro+1] == 61){
                    listagem.add(new Token(TokenAll.MENOR_IGUAL, "<=", linha, TokenType.OPERADOR));
                    ponteiro++;
                }else{
                    listagem.add(new Token(TokenAll.MENOR, "<", linha, TokenType.OPERADOR));
                }
                break;
            case 61:
                if(!this.isFimPonteiro() && fonte[ponteiro+1] == 61){
                    listagem.add(new Token(TokenAll.IGUALDADE, "==", linha, TokenType.OPERADOR));
                    ponteiro++;
                }else{
                    listagem.add(new Token(TokenAll.ATRIBUICAO, "=", linha, TokenType.OPERADOR));
                }
                break;
            case 62:
                if(!this.isFimPonteiro() && fonte[ponteiro+1] == 61){
                    listagem.add(new Token(TokenAll.MAIOR_IGUAL, ">=", linha, TokenType.OPERADOR));
                    ponteiro++;
                }else{
                    listagem.add(new Token(TokenAll.MAIOR, ">", linha, TokenType.OPERADOR));
                }
                break;
            case 124:
                try{
                    if(fonte[ponteiro+1]==124){
                        listagem.add(new Token(TokenAll.OU, "||", linha, TokenType.OPERADOR));
                        ponteiro++;
                    }else{
                        caractereInvalido();
                        ESTADO = 0;
                    }
                }catch(ArrayIndexOutOfBoundsException a){
                    this.listErro.add(new Erros("OPERADOR INVÁLIDO", String.valueOf(fonte[ponteiro]), linha));
                    ESTADO = 0;
                }
                break;
        }//fim do switch/case
        ESTADO = 0;
    }//fim do método operador.

    /**
     * Determina algum caractere inválido.
     */
    public void caractereInvalido(){
        System.out.println("::Entrou em caractere invalido!");
        if(!this.isValidChar(fonte[ponteiro])){
            System.out.println("Entrou aqui!!!!" + fonte[ponteiro]);
            try{
                if(fonte[ponteiro] == ' ' || this.isOperador(fonte[ponteiro+1]) || this.isDelimitador(fonte[ponteiro+1]) || this.isQuebra(fonte[ponteiro+1]))
                    this.listErro.add(new Erros("CARACTERE INVÁLIDO", String.valueOf(fonte[ponteiro]), linha));
                else
                    if(Character.isLetter(this.fonte[ponteiro+1]) || this.fonte[ponteiro+1] == '_')
                        this.identificador();
                else
                    if(Character.isDigit(fonte[ponteiro+1]))
                        this.numero();
            }catch(ArrayIndexOutOfBoundsException e){
                this.listErro.add(new Erros("CARACTERE INVÁLIDO", String.valueOf(fonte[ponteiro]), linha));
            }//fim do try/catch
        }//fim do if
        ESTADO = 0;

    }

    /**
     * Verifica se um determinado caractere é um operador.
     * @param c
     * Caractere a ser verificado.
     * @return
     * Verdadeiro se for operador e falso caso contrário.
     */
    public boolean isOperador(char c) {

        if (c == 33 || c == 38
                || c == 42 || c == 43
                || c == 45 || c == 60
                || c == 61 || c == 62
                || c == 124 || c == 46)
            return true;
    return false;
    }

    /**
     * Verifica se um determinado caractere é um delimitador.
     * @param c
     * Caractere a ser verificado.
     * @return
     * Verdadeiro se for delimitador e falso caso contrário.
     */
    public boolean isDelimitador(char c) {
        if (c == 59 || c == 40
                || c == 41 || c == 91
                || c == 93 || c == 123 || c == 125 || c == 44) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Determina e cataloga delimitadores em um código.
     */
    public void delimitador() {
        System.out.println("::Entrou em delimitador!");
        switch (fonte[ponteiro]) {
            case 59:
                listagem.add(new Token(TokenAll.PONTOEVIRGULA, ";", linha, TokenType.DELIMITADOR));              // Delimitador ;
                break;
            case 40:
                listagem.add(new Token(TokenAll.ABREPARENTESES, "(", linha, TokenType.DELIMITADOR));             // Delimitador (
                break;
            case 41:
                listagem.add(new Token(TokenAll.FECHAPARENTESES, ")", linha, TokenType.DELIMITADOR));             // Delimitador )
                break;
            case 44:
                listagem.add(new Token(TokenAll.VIRGULA, ",", linha, TokenType.DELIMITADOR));             // Delimitador )
                break;
            case 46:
                listagem.add(new Token(TokenAll.PONTO, ".", linha, TokenType.DELIMITADOR));
                break;
            case 91:
                listagem.add(new Token(TokenAll.ABRECHOCHETES, "[", linha, TokenType.DELIMITADOR));             // Delimitador [
                break;
            case 93:
                listagem.add(new Token(TokenAll.FECHACOCHETES, "]", linha, TokenType.DELIMITADOR));             // Delimitador ]
                break;
            case 123:
                listagem.add(new Token(TokenAll.ABRECHAVES, "{", linha, TokenType.DELIMITADOR));             // Delimitador {
                break;
            case 125:
                listagem.add(new Token(TokenAll.FECHACHAVES, "}", linha, TokenType.DELIMITADOR));             // Delimitador }
                break;
        }
        ESTADO = 0;
    }

    /**
     * Determina comentários no código.
     */
   public void comentario(){

        System.out.println("::Entrou em comentario!");
        int flag = 0;
        if(coment == 1)
            ponteiro--;

        while(fonte[ponteiro] != 42 || fonte[ponteiro+1] != 47){
            this.incrementaPonteiro();
            if(ponteiro+1 == fonte.length){
                flag = 1;
                break;
            }
            coment = 0;
        }


        if(flag == 1){
            coment = 1;
        }else
            coment = 0;

        this.incrementaPonteiro();
        ESTADO = 0;
    }//fim do método comentário.

    /**
     * Determina uma cadeia constante.
     */
    private void cadeiaConstante(){
        System.out.println("::Entrou em cadeia constante!");
        int inicio = ponteiro;

        try{
            do{
                ponteiro++;//incrementa até achar aspas duplas.
            }while(fonte[ponteiro] != 34);

            //pega o conteúdo da cadeia constante.
            String str = new String(fonte).substring(inicio, ponteiro+1);
            listagem.add(new Token(TokenAll.CADEIA_CONSTANTE, str, linha, TokenType.CADEIA_CONSTANTE));
        }catch(ArrayIndexOutOfBoundsException e){
            this.listErro.add(new Erros("FINAL DA CADEIA CONSTANTE NÃO ENCONTRADO!", "\"", linha));
        }//fim do try/catch
        ESTADO = 0;
    }//fim do método cadeiaConstante

    /**
     * Determina um caractere constante.
     */
    private void caractereConstante(){
        System.out.println("::Entrou em caractere constante!");
        int temp = 0;
        this.incrementaPonteiro();

        String aux = "\'" + fonte[ponteiro];
        this.incrementaPonteiro();

        if(fonte[ponteiro] != 39){
            while(fonte[ponteiro] != 39){
                aux +=fonte[ponteiro];
                if(ponteiro == fonte.length-1){
                    temp = 1;
                    break;
                }//fim do if 2
            }//fim do while
            aux += "\'";
            if(temp==0){
                this.listErro.add(new Erros("FORMATO INVÁLIDO", aux, linha));
            }else
                this.listErro.add(new Erros("FINAL DO CARACTERE NÃO ENCONTRADO!", aux, linha));
        }else{
            aux += "\'";
            listagem.add(new Token(TokenAll.CHAR, aux, linha, TokenType.CHAR));
        }//fim do if/else
    }//fim do método caractereConstante

    public void imprimeLista()
    {
        //IMPRIMINDO A LISTAGEM
        System.out.println("\n::Lista de Tokens \n");
        for (Token obj : listagem) {
            TokenAll tipo = obj.getTipo();
            if (tipo.toString().equals("NUMERO")) {
                System.out.println("tipo: " + obj.getTipo() + "\n    valor: " + obj.getValorNumero() + "\n    linha: " + obj.getLinha());
            } else {
                System.out.println("tipo: " + obj.getTipo() + "\n    valor: " + obj.getValor() + "\n    linha: " + obj.getLinha());
            }
        }
        System.out.println("\n=================\n::Lista de Erros\n");
        for (Erros obj : this.listErro) {
            System.out.println("tipo " + obj.getTipo() + ", valor " + obj.getValor() + ", linha " + obj.getLinha());
        }
    }

}//fim da classe controleAutomato
